/**
 * 
 */
package com.williamoneil.charts.client.widget;

import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.TouchCancelEvent;
import com.google.gwt.event.dom.client.TouchCancelHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchEvent;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.williamoneil.charts.client.Charts;
import com.williamoneil.charts.client.controller.ChartController;
import com.williamoneil.charts.client.data.BenchmarkAxis;
import com.williamoneil.charts.client.data.BenchmarkLine;
import com.williamoneil.charts.client.data.ChartBoundsRect;
import com.williamoneil.charts.client.data.ChartElementsData;
import com.williamoneil.charts.client.data.ChartType;
import com.williamoneil.charts.client.data.PriceAxis;
import com.williamoneil.charts.client.data.RelativeStrengthLine;
import com.williamoneil.charts.client.data.Tick;
import com.williamoneil.charts.client.util.ChartUtil;
import com.williamoneil.charts.client.util.Colors;

/**
 * @author gudipati
 *
 */

enum MOUSE_STATE {
	NORMAL,
	TRACKPRICE,
	BENCHMARK_MOVE,
	RS_MOVE;
}

public class ChartLayer extends ChartOverlay {

	private PriceVolumeLayer priceVolumeLayer = null;
	private GridLinesLayer gridLinesLayer = null;
	private BenchmarkLineLayer benchmarkLineLayer = null;
	private RelativeStrengthLineLayer rsLineLayer = null;
	private EPSLineLayer epsLineLayer = null;
	
	
	private MOUSE_STATE mouseState = MOUSE_STATE.NORMAL;
	
	private ChartType chartType = ChartType.CANDLESTICK;
	private boolean isLinear = true;
	private int tickSpacing = ChartUtil.DEFAULT_TICK_SPACING;
	
	public ChartLayer(ChartBoundsRect chartBounds) {
		super(chartBounds);

		gridLinesLayer = new GridLinesLayer(chartBounds);
		priceVolumeLayer = new PriceVolumeLayer(chartBounds, chartType, tickSpacing);
		benchmarkLineLayer = new BenchmarkLineLayer(chartBounds);
		rsLineLayer = new RelativeStrengthLineLayer(chartBounds);
		epsLineLayer = new EPSLineLayer(chartBounds);
		
		final Element canvasElement = super.getCanvas().getElement();
		
		// if touch is supported then we register touch events instead of mouse
		if(TouchEvent.isSupported()) {
			super.getCanvas().addTouchStartHandler(new TouchStartHandler() {
				@Override
				public void onTouchStart(TouchStartEvent event) {
					event.preventDefault();
					event.stopPropagation();
					
					if(event.getTouches().length() == 1) {
						final int x = event.getTouches().get(0).getRelativeX(canvasElement);
						final int y = event.getTouches().get(0).getRelativeY(canvasElement);
						trackPriceOn(x, y);
					} else {
						//TODO: code for measurement tool heres
					}
					
				}			
			});
			
			super.getCanvas().addTouchEndHandler(new TouchEndHandler() {
				@Override
				public void onTouchEnd(TouchEndEvent event) {
					event.preventDefault();
					event.stopPropagation();

					if(mouseState == MOUSE_STATE.TRACKPRICE || event.getTouches() == null || event.getTouches().length() == 0) {
						trackPriceOff();
					}
				}
			});
			
			super.getCanvas().addTouchCancelHandler(new TouchCancelHandler() {
				@Override
				public void onTouchCancel(TouchCancelEvent event) {
					event.preventDefault();
					event.stopPropagation();

					trackPriceOff();
				}
			});
			
			super.getCanvas().addTouchMoveHandler(new TouchMoveHandler() {
				@Override
				public void onTouchMove(TouchMoveEvent event) {
					event.preventDefault();
					event.stopPropagation();

					if(event.getTouches().length() == 1) {
						final int x = event.getTouches().get(0).getRelativeX(canvasElement);
						final int y = event.getTouches().get(0).getRelativeY(canvasElement);
						trackPriceOn(x, y);
					} else {
						//TODO: measurement tool here
					}
					
				}
			});
			
		} else {
			// mouse-down would enable the track price
			super.getCanvas().addMouseDownHandler(new MouseDownHandler() {
				@Override
				public void onMouseDown(MouseDownEvent event) {
					if(event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
						ChartLayer.this.onMouseDown(event.getX(), event.getY());
					}
				}
			});
			
			// mouse-up would disable the track price
			super.getCanvas().addMouseUpHandler(new MouseUpHandler() {
				@Override
				public void onMouseUp(MouseUpEvent event) {
					ChartLayer.this.onMouseUp();
					ChartLayer.this.getCanvas().addStyleName("mouse-default");
				}
			});
			
			// mouse-out would disable the track price
			super.getCanvas().addMouseOutHandler(new MouseOutHandler() {
				@Override
				public void onMouseOut(MouseOutEvent event) {
					ChartLayer.this.onMouseOut();
					ChartLayer.this.getCanvas().addStyleName("mouse-default");
				}
			});
			
			// mouse-move would move the track price if it is enabled
			super.getCanvas().addMouseMoveHandler(new MouseMoveHandler() {
				@Override
				public void onMouseMove(MouseMoveEvent event) {
					if(event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
						if(mouseState == MOUSE_STATE.TRACKPRICE) {
							// mouse moved with left-button pressed
							trackPriceOn(event.getX(), event.getY());
						} else if (mouseState == MOUSE_STATE.BENCHMARK_MOVE) {
							// mouse moved with benchmark-line selected
							moveBenchmarkTo(event.getY());
						} else if (mouseState == MOUSE_STATE.RS_MOVE) {
							// mouse moved with rs-line selected
							//TODO
						}
					} else {
						final BenchmarkLine aBL = getBenchmarkLineAt(event.getX(), event.getY());
						if(aBL != null) {
							ChartLayer.this.getCanvas().addStyleName("mouse-move-updown");
							//ChartLayer.this.getCanvas().removeStyleName("mouse-default");
						} else {
							ChartLayer.this.getCanvas().addStyleName("mouse-default");
						}
						// show the hover for the lines here
						//TODO:
					}
				}
			});
		}
	}
	
	/* (non-Javadoc)
	 * @see com.williamoneil.charts.client.widget.ChartOverlay#drawOverlay()
	 */
	@Override
	public void drawOverlay() {		
		gridLinesLayer.drawOverlay();
		priceVolumeLayer.drawOverlay();
		benchmarkLineLayer.drawOverlay();
		rsLineLayer.drawOverlay();
		epsLineLayer.drawOverlay();
		
		drawChart();
	}
	
	private void drawChart() {
		if(super.getChartElements() == null) {
			return;
		}
		
		super.getGraphicsContext().drawImage(gridLinesLayer.getGraphicsContext().getCanvas(), 0, 0);
		super.getGraphicsContext().drawImage(benchmarkLineLayer.getGraphicsContext().getCanvas(), 0, 0);
		super.getGraphicsContext().drawImage(rsLineLayer.getGraphicsContext().getCanvas(), 0, 0);
		super.getGraphicsContext().drawImage(priceVolumeLayer.getGraphicsContext().getCanvas(), 0, 0);
		super.getGraphicsContext().drawImage(epsLineLayer.getGraphicsContext().getCanvas(), 0, 0);
		// save last updated state
		//super.getGraphicsContext().save();
	}

	private void clearAndDrawChart() {
		super.clearCanvas();
		drawChart();
	}
	
	private void trackPriceOn(final int x, final int y) {
		if(super.getChartElements() == null) {
			return;
		}

		try {			
			final ChartBoundsRect bounds = super.getChartBoundsRect();
			
			if(!bounds.isWithinBounds(x, y)) {
				mouseState = MOUSE_STATE.NORMAL;
				return;
			}
			
			if(mouseState == MOUSE_STATE.TRACKPRICE) {
				// track-price is already on, this is from track-price move event
				
				// draw latest chart
				clearAndDrawChart();
			}
			
			mouseState = MOUSE_STATE.TRACKPRICE;
			
			final Context2d context = super.getGraphicsContext();
			final TrackPricePanel trackPricePanel = TrackPricePanel.getInstance(bounds, super.getChartElements());
			trackPricePanel.showAt(x, y, context, this);
		}catch(Exception ex) {
			Charts.showError(ex);
		}
	}

	private void trackPriceOff() {
		try {
			mouseState = MOUSE_STATE.NORMAL;
		
			final TrackPricePanel trackPricePanel = TrackPricePanel.getInstance(super.getChartBoundsRect(), super.getChartElements());
			trackPricePanel.hide();
		
			// draw latest chart
			clearAndDrawChart();
		}catch(Exception ex) {
			Charts.showError(ex);
		}
	}
	
	@Override
	public void resetData(ChartElementsData chartElements) {
		super.resetData(chartElements);

		this.gridLinesLayer.resetData(chartElements);
		this.priceVolumeLayer.resetData(chartElements);
		
		this.benchmarkLineLayer.resetData(chartElements);
		this.rsLineLayer.resetData(chartElements);
		this.epsLineLayer.resetData(chartElements);
	}
	
	public void clearAllCanvas() {
		super.clearCanvas();
		
		// also clear grid and price-volume canvas
		this.gridLinesLayer.clearCanvas();
		this.priceVolumeLayer.clearCanvas();
		this.benchmarkLineLayer.clearCanvas();
		this.rsLineLayer.clearCanvas();
		this.epsLineLayer.clearCanvas();
	}
	
	@Override
	public void resetChartBounds(ChartBoundsRect chartBounds) {
		super.resetChartBounds(chartBounds);
		
		gridLinesLayer.resetChartBounds(chartBounds);
		priceVolumeLayer.resetChartBounds(chartBounds);
		benchmarkLineLayer.resetChartBounds(chartBounds);
		rsLineLayer.resetChartBounds(chartBounds);
		epsLineLayer.resetChartBounds(chartBounds);
	}
	
	public void changeScale(final boolean isLinear) {
		this.isLinear = isLinear;
		// changing scale requires us to re-calculate the grid-lines and re-draw the entire data
		ChartElementsData elements = super.getChartElements();  
		ChartUtil.populateChartElements(elements, ChartController.getInstance().getBenchmarkChart(elements.getPeriodicity()), super.getChartBoundsRect(), isLinear, tickSpacing);
		this.resetData(elements);
	}

	int benchmarkLayerOffset = 0;
	private void moveBenchmarkTo(final int newY) {
		if(selectedBenchmarkLine == null) {
			mouseState = MOUSE_STATE.NORMAL;
			return;
		}
		final BenchmarkAxis benchmarkAxis = super.getChartElements().getBenchmarkAxis();
		
		final int oldY = benchmarkAxis.getY(selectedBenchmarkLine.getPrice(), super.getChartBoundsRect());
		
		if(newY-oldY != 0) {
			benchmarkLayerOffset += newY - oldY;
		
			// clear benchmark layer and redraw
			this.benchmarkLineLayer.clearCanvas();
			this.benchmarkLineLayer.drawOverlay();
			

			clearAndDrawChart();
		}
	}
	
	public final BenchmarkLine getBenchmarkLineAt(final int x, final int y) {
		final ChartElementsData elements = super.getChartElements();
		if(elements == null) {
			return null;
		}
		
		final BenchmarkAxis benchmarkAxis = elements.getBenchmarkAxis();
		
		final Tick tick = elements.getTickAtPoint(x); 
		if(tick != null) {
			final BenchmarkLine aBL = tick.getBenchmarkLine();
			if(aBL != null) {
				final int benchmarkY = benchmarkAxis.getY(aBL.getPrice(), super.getChartBoundsRect());
				
				if(LINE_HIT_TOLENRANCE >= Math.abs(y-benchmarkY)) {
					return aBL;
				}
			}
		}
		
		return null;
	}
	
	private BenchmarkLine selectedBenchmarkLine = null;
	private final static int LINE_HIT_TOLENRANCE = 5;
	public final void onMouseDown(final int x, final int y) {
		final ChartElementsData elements = super.getChartElements();
		if(elements == null) {
			return;
		}
		
		final ChartBoundsRect bounds = super.getChartBoundsRect();
		final PriceAxis priceAxis = elements.getPriceAxis();
		final BenchmarkAxis benchmarkAxis = elements.getBenchmarkAxis();
		
		final Tick tick = elements.getTickAtPoint(x); 
		if(tick != null) {
			final BenchmarkLine aBL = tick.getBenchmarkLine();
			if(aBL != null) {
				final int benchmarkY = benchmarkAxis.getY(aBL.getPrice(), super.getChartBoundsRect());
				
				if(LINE_HIT_TOLENRANCE >= Math.abs(y-benchmarkY)) {
					mouseState = MOUSE_STATE.BENCHMARK_MOVE;
					selectedBenchmarkLine = aBL;
				}
			}
			
			if(mouseState != MOUSE_STATE.BENCHMARK_MOVE) {
				final RelativeStrengthLine rsLine = tick.getRsLine();
				if(rsLine != null) {
					final int rsY = priceAxis.getY(rsLine.getPrice(), super.getChartBoundsRect());
					
					if(LINE_HIT_TOLENRANCE >= Math.abs(y-rsY)) {
						mouseState = MOUSE_STATE.RS_MOVE;
					}	
				}
			}
		}

		if(bounds.isWithinBounds(x, y)) {
			if(mouseState == MOUSE_STATE.NORMAL || mouseState == MOUSE_STATE.TRACKPRICE) {
				mouseState = MOUSE_STATE.TRACKPRICE;
				trackPriceOn(x, y);
			}
		} else if (bounds.isWithinYScale(x,y)) {
			this.changeScale(!isLinear);
		}
	}
	
	public final void onMouseUp() {
		if(mouseState == MOUSE_STATE.TRACKPRICE) {
			trackPriceOff();
		} else if (mouseState == MOUSE_STATE.BENCHMARK_MOVE) {
			selectedBenchmarkLine = null;
		}
		
		mouseState = MOUSE_STATE.NORMAL;
	}
	
	public final void onMouseOut() {
		if(mouseState == MOUSE_STATE.TRACKPRICE) {
			trackPriceOff();
		} else if (mouseState == MOUSE_STATE.BENCHMARK_MOVE) {
			selectedBenchmarkLine = null;
		}
		
		mouseState = MOUSE_STATE.NORMAL;

	}
	
	public final void changeTheme(final boolean light) {
		if(light != Colors.isLightTheme()) {
			Colors.changeColorTheme(light);
			this.clearAllCanvas();
			this.drawOverlay();
		}
	}
	
	public final void changeTickSpacing(final int tickSpacing) {
		if(this.tickSpacing != tickSpacing) {
			this.tickSpacing = tickSpacing;
			//super.getChartElements().getDateAxis().
			this.priceVolumeLayer.setTickSpacing(tickSpacing);
			
			this.clearAllCanvas();
			this.drawOverlay();
		}
	}
	
	public final void changeChartType(final ChartType cType) {
		if(cType != null && cType != this.chartType) {
			this.chartType = cType;
			
			// clear only the price chart 
			this.priceVolumeLayer.clearCanvas();
			this.priceVolumeLayer.setChartType(chartType);
			this.priceVolumeLayer.drawOverlay();
			
			this.clearAndDrawChart();
		}
	}

	/**
	 * @return the priceVolumeLayer
	 */
	public PriceVolumeLayer getPriceVolumeLayer() {
		return priceVolumeLayer;
	}

	/**
	 * @return the gridLinesLayer
	 */
	public GridLinesLayer getGridLinesLayer() {
		return gridLinesLayer;
	}

	/**
	 * @return the benchmarkLineLayer
	 */
	public BenchmarkLineLayer getBenchmarkLineLayer() {
		return benchmarkLineLayer;
	}

	/**
	 * @return the rsLineLayer
	 */
	public RelativeStrengthLineLayer getRsLineLayer() {
		return rsLineLayer;
	}
	
	/**
	 * @return the epsLineLayer
	 */
	public EPSLineLayer getEpsLineLayer() {
		return epsLineLayer;
	}

	/**
	 * @return the isLinear
	 */
	public boolean isLinear() {
		return isLinear;
	}

	/**
	 * @return the tickSpacing
	 */
	public int getTickSpacing() {
		return tickSpacing;
	}
}